<template>
  <div class="infinite-view-container" ref="wrapper" @scroll="onScroll">
    <div class="infinite-phantom" :style="{ height: totalHeight }"></div>
    <div
      class="cell-container"
      ref="container"
      :style="{ transform: getTransform }"
    >
      <div
        class="cell-group"
        v-for="(cacheWrapper, index) in cacheWrappers"
        :key="index"
        :class="'box' + index"
      >
        <div class="cell" v-for="item in cacheWrapper" :key="item[keyType]">
          <img
            :src="item.img"
            :style="{ width: item.imgW + 'px', height: item.imgH + 'px' }"
          />
          <div class="msg" v-html="item.msg"></div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import Cache from './Cache'
import CacheManager from './CacheManager'
import './util'
let area = 0 // 记录滚动在第几区域
export default {
  components: {},
  props: {
    list: {
      type: Array,
      default: () => [],
    },
    keyType: {
      type: String,
      default: 'id',
    },
  },
  data() {
    return {
      startOffset: 0,
      cacheWrappers: [[], [], []], // 存放3组caches数组
      C: CacheManager,
      myList: [],
      lastY: 0,
      sections: [], // number数组 存放区间值
      scrollDir: 0, // -1 向下滚 1 向上滚
      totalHeight: 0,
    }
  },
  watch: {
    list(val) {
      this.myList = val.map((item, index) => {
        return { ...item, _infiniteId: item[this.keyType], _index: index }
      })
    },
  },
  created() {
    this.C.init()
    this.myList = this.list.map((item, index) => {
      return { ...item, _infiniteId: item[this.keyType], _index: index }
    })
  },
  mounted() {
    // 首屏加载
    this.startLoad()
  },
  methods: {
    // 首屏加载
    async startLoad() {
      await this.firstLoad(this.C.caches.length, 0)
      await this.firstLoad(this.C.caches.length, 1)
      await this.firstLoad(this.C.caches.length, 2)
    },
    // 更新界定范围
    updateSection() {
      const cacheWrappers = this.cacheWrappers
      let sections = []
      let cache
      cacheWrappers.forEach((value, index) => {
        if (value.length > 0) {
          cache = this.C.getCache(value[0]._infiniteId)
          sections.push(cache.top)
          if (index === cacheWrappers.length - 1) {
            cache = this.C.getCache(value[value.length - 1]._infiniteId)
            sections.push(cache.top + cache.height)
          }
        }
      })
      this.sections = sections
      // 更新总高度
      this.updateTotalHeight()
    },
    updateTotalHeight() {
      const last = this.C.lastCache
      if (last && this.C.caches.length === this.myList.length) {
        this.totalHeight = `${last.top + last.height}px`
        return
      }
      this.totalHeight = `${this.C.estimateHeight * this.myList.length}px`
    },
    // 判断在第几区域
    getSectionArea(scrollTop) {
      const sections = JSON.parse(JSON.stringify(this.sections))
      let area = 0
      let section = sections.shift()
      while (section !== undefined) {
        if (scrollTop < section) {
          break
        }
        area++
        section = sections.shift()
      }
      return area
    },
    // 预加载列表 直到加载到超出屏幕的尺寸
    firstLoad(start = 0, idx) {
      return new Promise((resolve, reject) => {
        ;(async () => {
          try {
            let loadList = this.myList.slice(start, start + 2)
            if (loadList.length === 0) {
              resolve()
              return
            }
            // 合并数组
            let target = this.cacheWrappers[idx] || []
            target = target.concat(loadList)
            // this.visibleList = loadList
            this.$set(this.cacheWrappers, idx, target)
            this.$nextTick().then(() => {
              // 去更新cache
              let lastCache = this.C.lastCache
              let cellDoms = this.$refs.container.children[idx].children
              const startIdx = target.length - loadList.length
              loadList.forEach((item, index) => {
                if (!this.C.getCache(item._infiniteId)) {
                  let cache = new Cache({
                    top: lastCache ? lastCache.top + lastCache.height : 0,
                    height: cellDoms[startIdx + index]
                      ? cellDoms[startIdx + index].offsetHeight
                      : this.C.estimateHeight,
                  })
                  this.C.setCache(item._infiniteId, cache)
                  lastCache = cache
                }
              })
              // 手动更新范围
              this.updateSection()
              if (
                this.$refs.container.children[idx].offsetHeight <
                this.$refs.wrapper.offsetHeight
              ) {
                this.firstLoad(start + 2, idx).then(() => {
                  resolve()
                  return
                })
              } else {
                resolve()
                return
              }
            })
            // await this.$nextTick()
          } catch (error) {
            console.log(error)
            reject(error)
          }
        })()
      })
    },
    // 加载后面的页面
    loadNextPage() {
      const wrapperLength = this.cacheWrappers.length
      const lastWrapper =
        wrapperLength > 0 ? this.cacheWrappers[wrapperLength - 1] : null
      let start =
        lastWrapper && lastWrapper.length > 0
          ? lastWrapper[lastWrapper.length - 1]._index + 1
          : 0
      let count = Math.ceil(
        this.$refs.wrapper.offsetHeight / this.C.estimateHeight
      )
      let loadList = this.myList.slice(start, start + count)
      if (loadList.length === 0) {
        return
      }
      // 删除第一页 新增最后一页
      this.cacheWrappers.staticShift()
      this.cacheWrappers.push(loadList)
      this.$nextTick().then(() => {
        // 更新cache
        let lastCache = this.C.lastCache
        const idx = this.cacheWrappers.length - 1
        const cellDoms = this.$refs.container.children[idx].children
        let cache
        loadList.forEach((item, index) => {
          if (!this.C.getCache(item._infiniteId)) {
            cache = new Cache({
              top: lastCache ? lastCache.top + lastCache.height : 0,
              height: cellDoms[index]
                ? cellDoms[index].offsetHeight
                : this.C.estimateHeight,
            })
            this.C.setCache(item._infiniteId, cache)
            lastCache = cache
          }
        })
        // 手动更新范围
        this.updateSection()
      })
    },
    // 加载前面的页面
    loadPrePage() {
      if (this.cacheWrappers.length === 0) return
      if (this.cacheWrappers[0].length === 0) return
      let lastIdx = this.cacheWrappers[0][0]._index
      let count = Math.ceil(
        this.$refs.wrapper.offsetHeight / this.C.estimateHeight
      )
      if (lastIdx < count) {
        count = lastIdx
      }
      let start = lastIdx - count
      let loadList = this.myList.slice(start, start + count)
      if (loadList.length === 0) {
        return
      }
      // 删除最后一页 新增第一页
      this.cacheWrappers.pop()
      this.cacheWrappers.unshift(loadList)
      // 手动更新范围
      this.updateSection()
    },
    async onScroll(e) {
      let { scrollTop } = e.target
      area = this.getSectionArea(scrollTop)
      // 向下滚
      if (this.lastY > scrollTop) {
        this.scrollDir = -1
      } else {
        // 向上滚
        this.scrollDir = 1
      }
      if (this.scrollDir > 0) {
        if (area > 1) {
          this.loadNextPage()
          if (area >= 4) {
            // 如果一下子滚动的很快 直接跳到范围外了  那么手动让滚动位置回复到中间
            this.$refs.wrapper.scrollTop = this.C.getCache(
              this.cacheWrappers[1][0]._infiniteId
            ).top
          }
        }
      }
      if (this.scrollDir < 0) {
        if (area <= 1) {
          this.loadPrePage()
          if (area <= 0) {
            // 如果一下子滚动的很快 直接跳到范围外了  那么手动让滚动位置回复到最上面
            this.$refs.wrapper.scrollTop = this.C.getCache(
              this.cacheWrappers[0][0]._infiniteId
            ).top
          }
        }
      }
      try {
        this.startOffset = this.C.getCache(
          this.cacheWrappers[0][0]._infiniteId
        ).top
      } catch (error) {
        console.log(error)
      }

      this.lastY = scrollTop
    },
  },
  computed: {
    getTransform() {
      return `translate3d(0, ${this.startOffset}px, 0)`
    },
  },
}
</script>

<style lang="scss" scoped>
.infinite-view-container {
  overflow: auto;
  position: relative;
  -webkit-overflow-scrolling: touch;
  text-align: left;
  .infinite-phantom {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
  }
  .box0 {
    border: 1px solid red;
  }
  .box1 {
    border: 1px solid blue;
  }
  .box2 {
    border: 1px solid yellow;
  }
}
</style>
